using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.IO;
using Serialization;
using SideShow.Channel.Messages;
using System.Reflection;

namespace SideShow.Channel
{
    public class DataReceivedEventArgs : EventArgs
    {
        private Message _msg;
        
        public DataReceivedEventArgs(Message msg)
        {
            _msg = msg;
        }

        public Message Message
        {
            get { return _msg; }
        }
    }

    public class ErrorEventArgs : EventArgs
    {
        private Exception _exception;

        public ErrorEventArgs(Exception ex)
        {
            _exception = ex;
        }

        public Exception Exception
        {
            get { return _exception; }
        }
    }


    public abstract class PocketPCChannelBase : IDisposable
    {
        private const int BufferSize = 4096;

        private TcpClient _client;
        private NetworkStream _ns;
        private byte[] _buffer = new byte[BufferSize];
        private int _msgSize = 0;
        private MemoryStream _msgBuffer = new MemoryStream();
        public event EventHandler<DataReceivedEventArgs> DataReceived;
        public event EventHandler Closed;
        public event EventHandler<ErrorEventArgs> Error;

        protected PocketPCChannelBase()
        {
            _client = new TcpClient();
        }

        protected PocketPCChannelBase(TcpClient client)
        {
            _client = client;
            _ns = _client.GetStream();
            _ns.BeginRead(_buffer, 0, BufferSize, ReadCallback, null);
        }

        ~PocketPCChannelBase()
        {
            Dispose();
        }

        public bool Connected
        {
            get
            {
                return _client != null && _client.Client.Connected;
            }
        }

        public void Open(IPAddress host, int port)
        {
            _client.Connect(host, port);
            _ns = _client.GetStream();
            _ns.BeginRead(_buffer, 0, BufferSize, ReadCallback, null);
        }

        public void Close()
        {
            _client.Close();
        }

        public void Send(byte[] buf)
        {
            if (_client.Client.Connected)
            {
                BinaryWriter writer = new BinaryWriter(_ns);
                writer.Write(buf.Length);
                _ns.Write(buf, 0, buf.Length);
            }
        }

        public void Send(Message msg)
        {
            using (MemoryStream ms = new MemoryStream())
            {
                BinaryWriter writer = new BinaryWriter(ms);
                writer.Write(msg.GetType().FullName + ", " + msg.GetType().Module.Assembly.GetName().Name);
                msg.Serialize(writer);
                Send(ms.ToArray());
            }
        }

        private void ReadCallback(IAsyncResult result)
        {
            if (!result.IsCompleted || !this.Connected)
            {
                return;
            }
            try
            {
                int read = _ns.EndRead(result);
                using (MemoryStream ms = new MemoryStream())
                {
                    ms.Write(_buffer, 0, read);
                    if (read == BufferSize)
                    {
                        while (_ns.DataAvailable)
                        {
                            read = _ns.Read(_buffer, 0, BufferSize);
                            ms.Write(_buffer, 0, read);
                        }
                    }
                    ms.WriteTo(_msgBuffer);
                }

                while (_msgBuffer.Length > 0 && _msgBuffer.Length >= _msgSize)
                {
                    if (_msgSize == 0)
                    {
                        byte[] b = _msgBuffer.ToArray();
                        _msgSize = BitConverter.ToInt32(b, 0);
                        _msgBuffer = new MemoryStream(_msgSize);
                        _msgBuffer.Write(b, 4, b.Length - 4);
                    }
                    if (_msgBuffer.Length >= _msgSize)
                    {
                        _msgBuffer.Position = 0;
                        BinaryReader reader = new BinaryReader(_msgBuffer);
                        string typeName = reader.ReadString();
                        Type msgType = SerializableObject.FindType(typeName);
                        Message msg = (Message)Activator.CreateInstance(msgType);
                        msg.Deserialize(reader);
                        long restLength = _msgBuffer.Length - _msgBuffer.Position;
                        byte[] b = new byte[restLength];
                        _msgBuffer.Read(b, 0, b.Length);
                        _msgBuffer = new MemoryStream();
                        _msgBuffer.Write(b, 0, b.Length);
                        _msgSize = 0;
                        OnDataReceived(msg);
                    }
                }
                _ns.BeginRead(_buffer, 0, BufferSize, ReadCallback, null);
            }
            catch(IOException ex)
            {
                this.Close();
                OnError(ex);
                OnClosed();
            }
            catch(Exception ex)
            {
                OnError(ex);
            }
        }

        protected virtual void OnError(Exception ex)
        {
            if (Error != null)
            {
                Error(this, new ErrorEventArgs(ex));
            }
        }

        protected virtual void OnClosed()
        {
            if (Closed != null)
            {
                Closed(this, EventArgs.Empty);
            }
        }

        protected virtual void OnDataReceived(Message msg)
        {
            if (DataReceived != null)
            {
                DataReceived(this, new DataReceivedEventArgs(msg));
            }
        }

        


        #region IDisposable Members

        public void Dispose()
        {
            Close();
            GC.SuppressFinalize(this);
        }

        #endregion
    }

}
